/*
 * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine.
 *
 * Copyright (c) 2011 - 2014 Michael Andonie and contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package ea;

import ea.internal.collision.Collider;
import ea.internal.collision.ColliderGroup;
import ea.internal.gra.Listung;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Vector;

/**
 * Ein Knoten ist eine Sammlung vielen Raum-Objekten, die hierdurch einheitlich bewegt, und
 * einheitlich behandelt werden koennen.
 *
 * @author Michael Andonie, Niklas Keller <me@kelunik.com>
 */
public class Knoten extends Raum implements Listung {
	/**
	 * Die Liste aller Raum-Objekte, die dieser Knoten fasst.
	 */
	private Vector<Raum> list;

	/**
	 * Konstruktor für Objekte der Klasse Knoten
	 */
	public Knoten () {
		list = new Vector<>();
	}

	/**
	 * Entfernt alle Raum-Objekte von diesem Knoten, die an diesem Knoten gelagert sind.<br /> <br
	 * /> <b>ACHTUNG</b><br /> Sollte <i>Physik</i> benutzt werden:<br /> Diese Methode macht alle
	 * abgemeldeten <code>Raum</code>-Objekt fuer die Physik neutral!!!<br /> Sollte dies NICHT
	 * gewuenscht sein, gibt es hierfuer die Methode <code>leerenOhnePhysikAbmelden()</code>.
	 *
	 * @see #leerenOhnePhysikAbmelden()
	 */
	public void leeren () {
		for (int i = list.size() - 1; i >= 0; i--) {
			list.get(i).neutralMachen();
			list.get(i).loeschen();
		}

		list.clear();
	}

	/**
	 * Loescht alle Raum-Objekte, die an diesem Knoten gelagert sind, ohne sie jedoch von ihrer
	 * Physik her zu beeinflussen.
	 */
	public void leerenOhnePhysikAbmelden () {
		list.clear();
	}

	/**
	 * Entfernt ein Raum-Objekt von diesem Knoten.<br /> War es mehrfach angesteckt, so werden alle
	 * Verbindungen geloescht, war es niemals angemeldet, so passiert <b>gar nichts</b>.<br /> <br
	 * /> <b>Achtung!!</b><br /> Sollte <i>Physik</i> benutzt werden:<br /> Diese Methode macht alle
	 * abgemeldeten <code>Raum</code>-Objekt fuer die Physik neutral!!!<br /> Sollte dies NICHT
	 * gewuenscht sein, gibt es hierfuer die Methode <code>entfernenOhnePhysikAbmelden()</code>.
	 *
	 * @param m
	 * 		Das von diesem Knoten zu entfernende Raum-Objekt
	 *
	 * @see #entfernenOhnePhysikAbmelden(Raum)
	 */
	public void entfernen (Raum m) {
		if (list.contains(m)) {
			m.neutralMachen();
			m.loeschen();
		}

		// noinspection StatementWithEmptyBody
		while (list.remove(m)) ;
	}

	/**
	 * Entfernt ein Raum-Objekt von diesem Knoten, <b>ohne seine Physik zu beeinflussen</b>.<br />
	 * War es mehrfach angesteckt, so werden alle Verbindungen geloescht, war es niemals angemeldet,
	 * so passiert <b>gar nichts</b>.
	 *
	 * @param m
	 * 		Das von diesem Knoten zu entfernende Raum-Objekt
	 */
	public void entfernenOhnePhysikAbmelden (Raum m) {
		list.remove(m);
	}

	/**
	 * Prueft, ob ein bestimmtes Raum-Objekt in diesem Knoten gelagert ist.<br /> <br />
	 * <b>ACHTUNG</b><br /> Diese Methode prueft nicht eventuelle Unterknoten, ob diese vielleiht
	 * das Raum-Objekt beinhalten, sondern nur den eigenen Inhalt!
	 *
	 * @param m
	 * 		Das Raum-Objekt, das auf Vorkommen in diesem Knoten ueberprueft werden soll
	 *
	 * @return <code>true</code>, wenn das Raum-Objekt <b>ein- oder auch mehrmals</b> an diesem
	 * Knoten liegt
	 */
	public boolean besitzt (Raum m) {
		return list.contains(m);
	}

	/**
	 * Kombinationsmethode. Hiermit kann man so viele Raum-Objekte gleichzeitig an den Knoten
	 * anmelden, wie man will.<br /> <b>Beispiel:</b><br /> <br /> <code> //Der Knoten, um alle
	 * Objekte zu sammeln<br /> Knoten knoten = new Knoten();<br /> <br /> //Lauter gebastelte
	 * Raum-Objekte<br /> Raum r1<br /> Raum r2;<br /> Raum r3;<br /> Raum r4;<br /> Raum r5<br />
	 * Raum r6;<br /> Raum r7;<br /> Raum r8;<br /> Raum r9<br /> Raum r10;<br /> Raum r11;<br />
	 * Raum r12;<br /> <br /> //Eine Methode, um alle anzumelden:<br /> knoten.add(r1, r2, r3, r4,
	 * r5, r6, r7, r8, r9, r10, r11, r12);<br /> </code><br /> Das Ergebnis: 11 Zeilen Programmcode
	 * gespart.
	 */
	public void add (Raum... m) {
		for (Raum n : m) {
			add(n);
		}
	}

	/**
	 * Fuegt ein Raum-Objekt diesem Knoten hinzu.<br /> Das zugefuegte Objekt wird ab dann in alle
	 * Methoden des Knotens (<code>verschieben(), dimension()</code> etc.) mit eingebunden.
	 *
	 * @param m
	 * 		Das hinzuzufuegende Raum-Objekt
	 */
	public void add (Raum m) {
		// reverse to keep backwardscompability
		Collections.reverse(list);

		list.add(m);

		Collections.reverse(list);
		Collections.sort(list);
	}

	/**
	 * Gibt alle Elemente des Knotens in Form eines <code>Raum</code>-Objekt-Arays aus.
	 *
	 * @return Alle Elemente als vollstaendig gefuelltes <code>Raum</code>-Objekt-Aray.
	 */
	public Raum[] alleElemente () {
		return list.toArray(new Raum[list.size()]);
	}

	/**
	 * Bewegt jedes angelegte Objekt für sich allein (Physik-Modus)!<br /> Das bedeutet, dass das
	 * Blockiertwerden eines einzelnen <code>Raum</code>-Objektes an diesem Knoten <b>nicht</b>
	 * automatisch alle anderen Objekte blockiert.
	 *
	 * @param v
	 * 		Der die Verschiebung beschreibende Vektor.
	 *
	 * @return Nur dann <code>true</code>, wenn bei allen anderen Objekten die Rueckgabe auch
	 * <code>true</code> ist. Sonst ist die Rueckgabe <code>false</code>.
	 */
	@Override
	public boolean bewegen (Vektor v) {
		boolean ret = true;

		for (int i = list.size() - 1; i >= 0; i--) {
			if (!list.get(i).bewegen(v)) {
				ret = false;
			}
		}

		return ret;
	}

	/**
	 * Zeichnet den Knoten.<br /> Das heisst, der Zeichnen-Befehl wird an die Unterobjekte
	 * weitergetragen.<br /> Diese Methode ist nur intern von Bedeutung
	 *
	 * @param g
	 * 		Das Grafik-Objekt
	 * @param r
	 * 		Das Rechteck, dass die Kameraposition definiert
	 */
	@Override
	public void zeichnen (Graphics2D g, BoundingRechteck r) {
		try {
			for (int i = list.size() - 1; i >= 0; i--) {
				list.get(i).zeichnenBasic(g, r);
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			// Wahrscheinlich wurde die Liste geleert.
		}
	}

	/**
	 * Die dimension()-Methode.<br /> Gibt ein <code>BoundingRechteck</code> aus, das alle
	 * Komponente dieses Knotens bedeckt.
	 *
	 * @return Das BoundingRechteck, das alle Komponente dieses Knotens bedeckt.<br /> Ist ein
	 * BoundingRechteck mit den Werten (0|0|0|0), wenn dieses Knoten keine Konkreten Raum-Objekte
	 * gesammelt hat.
	 */
	@Override
	public BoundingRechteck dimension () {
		BoundingRechteck ret = null;

		try {
			for (int i = list.size() - 1; i >= 0; i--) {
				if (ret == null) {
					ret = list.get(i).dimension();
				} else {
					ret = ret.summe(list.get(i).dimension());
				}
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			// Wahrscheinlich wurde die Liste geleert.
		}

		if (ret == null) {
			return new BoundingRechteck(0, 0, 0, 0);
		} else {
			return ret;
		}
	}

	/**
	 * Verschiebt diesen Knoten.<br /> Das heisst, dass saemtliche anliegenden Raum-Objekte
	 * gleichermassen Verschoben werden.
	 *
	 * @param v
	 * 		Der Vektor, der die Verschiebung angibt.
	 */
	@Override
	public void verschieben (Vektor v) {
		for (int i = list.size() - 1; i >= 0; i--) {
			list.get(i).verschieben(v);
		}
	}

	/**
	 * {@inheritDoc} Collider ist eine Gruppierung der Collider aller <code>Raum</code>-Objekte, die
	 * an diesem Knoten angehängt sind.
	 */
	@Override
	public Collider erzeugeCollider () {
		ColliderGroup group = new ColliderGroup();
		for (Raum r : list) {
			group.addCollider(r.erzeugeCollider());
		}
		return group;
	}

	/**
	 * Berechnet exakter alle rechteckigen Flächen, auf denen dieses Objekt liegt.<br /> Diese
	 * Methode wird von komplexeren Gebilden, wie geometrischen oder Listen ueberschrieben.
	 *
	 * @return Alle Rechtecksflächen, auf denen dieses Objekt liegt. Ist standardisiert ein Array
	 * der Größe 1 mit der <code>dimension()</code> als Inhalt.
	 */
	@Override
	public BoundingRechteck[] flaechen () {
		ArrayList<BoundingRechteck> data = new ArrayList<>();

		for (int i = list.size() - 1; i >= 0; i--) {
			data.addAll(Arrays.asList(list.get(i).flaechen()));
		}

		return data.toArray(new BoundingRechteck[data.size()]);
	}

	/**
	 * Setzt die Durchsichtigkeit für jedes angemeldete Objekt.
	 *
	 * @param opacity {@inheritDoc}
	 */
	@Override
	public void setOpacity (float opacity) {
		try {
			for (int i = list.size() - 1; i >= 0; i--) {
				list.get(i).setOpacity(opacity);
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			// Wahrscheinlich wurde die Liste geleert.
		}
	}
}
